package ru.compscicenter.uno.controller;

import ru.compscicenter.uno.log.Logger;
import ru.compscicenter.uno.net.TCPServer;
import ru.compscicenter.uno.net.UDPServer;
import ru.compscicenter.uno.model.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;

/**
 * User: SLeeP
 * Date: 04.11.11
 * Time: 18:35
 */
public class ServerController extends AController {
	private TCPServer tcpServer;
	private UDPServer udpServer;
    boolean toBroadcast = true;
    private static final String RULES_CONFIG_PATH = "res/rules.cfg";
    private int countOfMoves = 0;

	public ServerController(boolean toBroadcast, String name) {
        super(true);
        this.toBroadcast = toBroadcast;
		tcpServer = new TCPServer(this);
		tcpServer.setDaemon(true);
		tcpServer.start();
        Thread waitForPlayers = new Thread(new Runnable() {
            @Override
            public void run() {
                doWaitForPlayers();
            }
        });
        waitForPlayers.setDaemon(true);
        waitForPlayers.start();

        model = new Model();
    //    model.setMyPlayerId(0);
    //    model.addPlayer(new Player(name, 0));

		try {
            udpServer = new UDPServer();
        } catch (IOException e) {
            Logger.write("UDPServer Exception:" + e.toString(), Logger.Mode.ERROR);
        }

		RulesFileLoader rulesLoader = new RulesFileLoader(model.getRules());
		try {
			rulesLoader.load(RULES_CONFIG_PATH);
            Logger.write("Loaded rules", Logger.Mode.ACTION);
		} catch (IOException e) {
            Logger.write("Error while loading rules: " + e.toString(), Logger.Mode.ERROR);
		}

        //сразу же сохраним
        //этот блок надо будет переставить туда, где юзер нажмёт "далее" на экране выбора правил.
        try {
            rulesLoader.save(RULES_CONFIG_PATH);
            Logger.write("Saved rules", Logger.Mode.ACTION);
        } catch (IOException e) {
            Logger.write("Error while saving rules: " + e.toString(), Logger.Mode.ERROR);
        }
	}
    
    public int getPort() {
        return tcpServer.getPort();
    }

	public void startGameBroadcast(final String ip) {
		Thread broadcast = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Logger.write("Started broadcasting" , Logger.Mode.ACTION);
                    udpServer.doBroadcast(ip, tcpServer.getPort(),  "");
                } catch (IOException | InterruptedException e) {
                    Logger.write("Exception while broadcasting: " + e.toString(), Logger.Mode.ERROR);
                }
            }
        });
        broadcast.setDaemon(true);
        broadcast.start();
	}

    public ArrayList<String> getConnectedPlayersNames() {
        return tcpServer.getConnectedPlayersNames();
    }

	public void stopGameBroadcast() {
        Logger.write("Stopped broadcasting", Logger.Mode.ACTION);
		udpServer.stop();
        tcpServer.stopBroadcast();
	}

    public boolean isStopped() {
        return tcpServer.getIsStopped();
    }

    public void reStartServer() {
        tcpServer.reStartServer();
        Thread waitForPlayers = new Thread(new Runnable() {
            @Override
            public void run() {
                doWaitForPlayers();
            }
        });
        waitForPlayers.setDaemon(true);
        waitForPlayers.start();


    }

	public void run() {
        final String ip = whatIsMyIP();
        if (toBroadcast) {
            Thread broadcast = new Thread(new Runnable() {
                public void run() {
                    startGameBroadcast(ip);
                }
            });
            broadcast.setDaemon(true);
            broadcast.start();
            Logger.write("Server broadcast started", Logger.Mode.ACTION);
        } else {            
            //System.out.println("Not broadcasting! Waiting for users connected manually!");
            System.out.println("My IP: " + ip + ":" + tcpServer.getPort());
        }
		System.out.println("Type 'stop broadcast' to start the game");

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		try {
			while (!br.readLine().equalsIgnoreCase("stop broadcast")) {
			}
			stopGameBroadcast();
            Logger.write("Broadcast stopped", Logger.Mode.ACTION);
			initPlayers();
			startRound();
		} catch (IOException e) {
            Logger.write(e.toString(), Logger.Mode.ERROR);
            //e.printStackTrace();
		}
	}

	public void initPlayers() {
		//System.out.println("Connected players: ");
        tcpServer.stopBroadcast();
		ArrayList<String> names = tcpServer.getConnectedPlayersNames();
        for (int i = 0; i != tcpServer.getSockets().size(); i++) {
			addPlayer(new Player(names.get(i), i));
		}
	}

    public boolean startGame() {
        Logger.write("New game started", Logger.Mode.ACTION);
        try {
            return model.startGame();
        } catch (ModelException e) {
            Logger.write("Error while starting game: " + e, Logger.Mode.ERROR);
            return false;
        }
    }

	public boolean startRound() {
        for (Player player : model.getPlayers()) {
            player.dropAllCards();
        }
        boolean res = false;
        try {
            res = model.startRound();
            model.setCurrentPlayerId(0);
        } catch (ModelException e) {
            Logger.write("Error while starting round: " + e, Logger.Mode.ERROR);
            return false;
        }
        sendAllModelData();
        isReady = true;
        isChanged = true;
        Logger.write("New round started", Logger.Mode.ACTION);
		return res;
	}

    public void doWaitForPlayers() {
        tcpServer.doWaitForPlayers();
    }

    public void sendAllPlayersNames() {
        try {
            tcpServer.sendAllPlayersNames();
        } catch (IOException e) {
            Logger.write("Failed to send names for players: " + e.toString(), Logger.Mode.ERROR);
        }
    }

	private void sendAllModelData() {
		int i = 0;
		for (Socket socket : tcpServer.getSockets()) {
			String message = StringRepresentation.getStringRepresentation(i++, model);
            try {
                tcpServer.send(socket, message);
            } catch (IOException e) {
                Logger.write("Failed to send model data to players: " + e.toString(), Logger.Mode.ERROR);
            }
        }
        Logger.write("Model data is sent to players", Logger.Mode.ACTION);
	}

	public boolean doDropCard(int playerId, int cardId) {
        Card card = model.getPlayer(playerId).getCard(cardId);
        boolean success = false;
        try {
            success = model.doDropCard(playerId, cardId);
        } catch (ModelException e) {
            Logger.write("Error while dropping card " + cardId + " by player " + playerId + " :" + e, Logger.Mode.ERROR);
            return false;
        }
        if (success) {
            String command = DO_DROP_CARD + " " + playerId + " " + cardId;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
            isChanged = true;
            Logger.write("Player " + playerId + " dropped card: " + card.toString(), Logger.Mode.ACTION);
			return true;
		} else {
			return false;
		}
	}

	public boolean doDropCard(int playerId, int cardId, Card.Color selectedColor) {
        Card card = model.getPlayer(playerId).getCard(cardId);
        boolean success = false;
        try {
            success = model.doDropCard(playerId, cardId, selectedColor);
        } catch (ModelException e) {
            Logger.write("Error while dropping card " + cardId + " by player " + playerId + " :" + e, Logger.Mode.ERROR);
            return false;
        }
        if(success) {
            String command = DO_DROP_CARD + " " + playerId + " " + cardId + " " + selectedColor;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
            isChanged = true;
            Logger.write("Player " + playerId + " dropped card: " + card.toString() +
                    " new color = " + selectedColor, Logger.Mode.ACTION);
			return true;
		} else {
			return false;
		}
	}

	public boolean doTakeCard(int playerId) {
        boolean success = false;
        try {
            success = model.doTakeCard(playerId);
        } catch (ModelException e) {
            Logger.write("Error while taking card by player " + playerId + " :" + e, Logger.Mode.ERROR);
            return false;
        }
        if(success) {
            String command = DO_TAKE_CARD + " " + playerId;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
            isChanged = true;
            Logger.write("Player " + playerId + " took a card", Logger.Mode.ACTION);
			return true;
		} else {
			return false;
		}
	}

	public boolean doSayUno(int playerId) {
        boolean success = false;
        try {
            success = model.doSayUno(playerId);
        } catch (ModelException e) {
            Logger.write("Error while saying UNO by player " + playerId + " :" + e, Logger.Mode.ERROR);
            return false;
        }
        if(success) {
            String command = DO_SAY_UNO + " " + playerId;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
            isChanged = true;
            Logger.write("Player " + playerId + " said UNO", Logger.Mode.ACTION);
            return true;
        } else {
            return false;
        }
	}

	public boolean doSomebodyDidntSayUno(int playerId, int whoDidntSayUnoId) {
        boolean success = false;
        try {
            success = model.doSomebodyDidntSayUno(playerId, whoDidntSayUnoId);
        } catch (ModelException e) {
            Logger.write("Error while doSomebodyDidntSayUno() by player " + playerId + " :" + e, Logger.Mode.ERROR);
            return false;
        }
        if(success) {
            String command = DO_SAY_NO_UNO + " " + playerId + " " + whoDidntSayUnoId;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
			isChanged = true;
            Logger.write("Player " + playerId + " said, that player " +whoDidntSayUnoId
                    + "did not say UNO", Logger.Mode.ACTION);
			return true;
		} else {
			return false;
		}
	}

	public boolean doEndTurn(int playerId) {
		if (model.doEndTurn(playerId)) {
            String command = DO_END_TURN + " " + playerId;
            try {
                tcpServer.sendAll(command);
            } catch (IOException e) {
                Logger.write("Failed to send to all players: " + command + " " + e.toString(), Logger.Mode.ERROR);
            }
			isChanged = true;
            Logger.write("Player " + playerId + " finished turn", Logger.Mode.ACTION);
			return true;
		} else {
			return false;
		}
	}

    public void stopServer() {
        tcpServer.stopServer();
    }
}
